﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using Kyoh.App.Renamer.RenameEventArgs;
using Kyoh.Lib.Collections;

namespace Kyoh.App.Renamer
{
    public partial class RenameEntryCollection : Component, IBindingListView, ITypedList
    {
        private object _entriesLocker = new object();
        private static readonly Regex SerialRegex = new Regex(@"\?+", RegexOptions.Compiled);
        private static readonly Regex EscSeqRegex = new Regex(@"(?<=(?<!\\)(?:\\{2})*)\\(?:[fpx]|[acm]\<(?<format>[^\>]+)\>)", RegexOptions.Compiled);
        private readonly List<RenameEntry> _entries = new List<RenameEntry>();
        private readonly GenericMultiSorter<RenameEntry> _sorter = new GenericMultiSorter<RenameEntry>();
        private readonly Stack<RenameEntry> _undoList = new Stack<RenameEntry>();

        private RenameOptionCollection _batch;
        public event EventHandler<ListupProgressChangedEventArgs> ListupProgress;
        public event EventHandler<RenameProgressChangedEventArgs> RenameProgress;
        public event EventHandler<RenameFailedEventArgs> RenameFailed;
        public event EventHandler<DuplicatedPathEventArgs> DuplicatedPath;

        private readonly Dictionary<string, bool> userExcepted = new Dictionary<string, bool>();
        private readonly Dictionary<string, string> userNewNames = new Dictionary<string, string>();

        #region コンストラクタ/デストラクタ

        SynchronizationContext context;

        protected RenameEntryCollection() : this(null)
        {
        }

        protected RenameEntryCollection(IContainer components)
        {
            context = SynchronizationContext.Current;
            if (components != null)
                components.Add(this);
            InitializeComponent();
        }

        #endregion

        #region ユーザ入力の待避/復元

        /// <summary>ユーザ入力の待避</summary>
        protected void StoreUserOrder()
        {
            lock (_entriesLocker)
            {
                foreach (var entry in _entries)
                {
                    if (entry.UserHasExcepted)
                        AddOrReplace(userExcepted, entry.FullName, entry.UserExcepted);
                    if (entry.UserHasNewName)
                        AddOrReplace(userNewNames, entry.FullName, entry.UserNewName);
                }
            }
        }

        private static void AddOrReplace<TKey, TValue>(Dictionary<TKey, TValue> dict, TKey key, TValue value)
        {
            if (dict.ContainsKey(key))
                dict[key] = value;
            else
                dict.Add(key, value);
        }

        /// <summary>ユーザ入力の復元</summary>
        protected void RestoreUserOrder()
        {
            bool except;
            string newName;
            lock (_entriesLocker)
            {
                foreach (var entry in _entries)
                {
                    if (userExcepted.TryGetValue(entry.FullName, out except))
                        entry.Excepted = except;
                    if (userNewNames.TryGetValue(entry.FullName, out newName))
                        entry.NewName = newName;
                }
            }
        }

        public void RemoveUserSupplement(IndexReplacer index, object obj)
        {
            var entry = obj as RenameEntry;
            if (entry == null)
                return;
            userExcepted.Remove(entry.FullName);
            userNewNames.Remove(entry.FullName);
            entry.Excepted = false;
            entry.NewName = null;
            SetNewName(index, entry);
        }

        #endregion

        private void Invoke<TEventArgs>(EventHandler<TEventArgs> handler, TEventArgs e, bool asynchronized)
            where TEventArgs : EventArgs
        {
            try
            {
                if (asynchronized)
                    handler.BeginInvoke(this, e, null, null);
                else
                    handler.Invoke(this, e);
            }
            catch (Exception exc)
            {
                //TODO: 例外をどうする？
                Debug.WriteLine(exc);
            }
        }

        #region Listup

        protected void Listup(RenameOptionCollection batch)
        {
            _batch = batch;

            StoreUserOrder();
            lock (_entriesLocker)
                _entries.Clear();
            RaiseListChanged(ListChangedType.Reset, -1);
            _batch.ResetNumbers();
            if (!Path.IsPathRooted(_batch.Path) || !Directory.Exists(_batch.Path))
                return;

            lock (_entriesLocker)
                _entries.AddRange(ListupRecursive(new DirectoryInfo(_batch.Path), _batch.Depth, false).TakeWhile(e => !SuspendListup));
            RestoreUserOrder();
            Sort();
        }

        private IEnumerable<RenameEntry> ListupRecursive(DirectoryInfo directory, int depth, bool recursive)
        {
            if (directory == null)
                yield break;
            if (depth != _batch.Depth && _batch.ContainsDirectory && _batch.IsMatch(directory.Name))
                yield return new RenameEntry(directory, _iconManager);
            if (depth < 0)
                yield break;
         
            Invoke(ListupProgress, new ListupProgressChangedEventArgs(directory.FullName), true);

            IEnumerable<FileSystemInfo> list;
            try
            {
                list = directory.EnumerateFileSystemInfos();
            }
            catch (UnauthorizedAccessException)
            {
                yield break;
            }
            foreach (var info in list.Where(i => !i.Attributes.HasFlag(FileAttributes.System)))
            {
                var dir = info as DirectoryInfo;
                var file = info as FileInfo;
                if (file != null && _batch.ContainsFile && _batch.IsMatch(file.Name))
                    yield return new RenameEntry(file, _iconManager);
                if (dir != null)
                    foreach (var child in ListupRecursive(dir, depth - 1, true))
                        yield return child;
            }
        }

        public class IndexReplacer
        {
            public int Index;
            public RenameOption Option;
            public string ReplaceIndex(Match match)
            {
                return Option.GetNumber(Index, match.Length);
            }
        }

        private void SetNewName(IndexReplacer index, RenameEntry entry)
        {
            foreach (var option in _batch)
            {
                if (entry.Excepted)
                    continue;

                if (option.IsMatch(entry.SystemNewName))
                {
                    index.Option = option;
                    option.CreateNumber(entry.Directory);
                    entry.SystemNewName = EscSeqRegex.Replace(SerialRegex.Replace(option.Replace(entry.SystemNewName), index.ReplaceIndex), entry.ReplaceSequence);

                    if (option.Except)
                        entry.SystemExcepted = true;
                }
            }
        }

        protected bool SuspendListup { get; set; }

        #endregion

        #region Rename

        protected void Rename()
        {
            _undoList.Clear();

            lock (_entriesLocker)
            {
                if (RenameCore(_entries.Select(FixDestination), false))
                {
                    Debug.WriteLine("undo");
                    Undo();
                }
            }
        }

        private bool RenameCore(IEnumerable<RenameEntry> entries, bool undo)
        {
            foreach (
                var entry in
                    entries.Where(e => e.FullName != e.Destination).OrderBy(GetRenameOrder))
            {
                if (!undo && SuspendRename)
                    return true;

                bool overwrite = false;
                // 重複チェック・ユーザ確認
                switch (CheckDuplication(entry))
                {
                    case DuplicatedPathEventArgs.DuplicatedPathMethod.Skip:
                        continue;
                    case DuplicatedPathEventArgs.DuplicatedPathMethod.Undo:
                        return true;
                    case DuplicatedPathEventArgs.DuplicatedPathMethod.Abort:
                        return false;
                    case DuplicatedPathEventArgs.DuplicatedPathMethod.OverWrite:
                        overwrite = true;
                        break;
                }

                if (!undo && SuspendRename)
                    return true;

                // ファイル名変更・ユーザ確認
                switch (RenameAndRetry(entry, overwrite))
                {
                    case RenameFailedEventArgs.RenameResultMethod.Skip:
                        continue;
                    case RenameFailedEventArgs.RenameResultMethod.Undo:
                        return true;
                    case RenameFailedEventArgs.RenameResultMethod.Abort:
                        return false;
                }

                // undoリストに追加する
                if (!undo)
                    _undoList.Push(entry);
            }
            return false;
        }

        private DuplicatedPathEventArgs.DuplicatedPathMethod CheckDuplication(RenameEntry entry)
        {
            var method = DuplicatedPathEventArgs.DuplicatedPathMethod.Retry;
            while (!SuspendRename && (File.Exists(entry.Destination) || Directory.Exists(entry.Destination)) &&
                   method == DuplicatedPathEventArgs.DuplicatedPathMethod.Retry)
            {
                var e = new DuplicatedPathEventArgs(entry.FullName, entry.Destination);
                this.Invoke(DuplicatedPath, e, false);
                method = e.Method;
            }
            if (method == DuplicatedPathEventArgs.DuplicatedPathMethod.Number)
                entry.Destination = GetNumberedPath(entry.Destination);
            return method;
        }

        private static string GetNumberedPath(string path)
        {
            string newPath, middle = string.Empty, tail = Path.GetExtension(path);
            var head = Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(path));
            var index = 2;
            do
            {
                middle = string.Format("({0})", index++);
                newPath = head+middle+tail;
            } while (File.Exists(newPath) || Directory.Exists(newPath));
            return newPath;
        }

        private RenameFailedEventArgs.RenameResultMethod RenameAndRetry(RenameEntry entry, bool overwrite)
        {
            var method = RenameFailedEventArgs.RenameResultMethod.Retry;
            while (method == RenameFailedEventArgs.RenameResultMethod.Retry)
            {
                try
                {
                    entry.RenameHandler.Invoke(entry.FullName, entry.Destination, overwrite);
                    var e = new RenameProgressChangedEventArgs(entry.Directory, entry.CurrentName, entry.Destination);
                    Invoke(RenameProgress, e, true);
#if DEBUG
                    //Thread.Sleep(500);
#endif
                    method = RenameFailedEventArgs.RenameResultMethod.Success;
                }
                catch (Exception exc)
                {
                    var e = new RenameFailedEventArgs(entry.FullName, entry.Destination, exc.Message);
                    Invoke(RenameFailed, e, true);
                    method = e.Method;
                }
            }
            return method;
        }

        private static int GetRenameOrder(RenameEntry entry)
        {
            return -1*entry.FullName.TrimEnd(Path.DirectorySeparatorChar)
                          .Count(c => c == Path.DirectorySeparatorChar);
        }

        private RenameEntry FixDestination(RenameEntry entry, int itemIndex)
        {
            entry.Destination = Path.Combine(entry.Directory, entry.NewName);
            return entry;
        }

        public void Undo()
        {
            RenameCore(_undoList.Select(SwitchUndo), true);
            _undoList.Clear();
        }

        private RenameEntry SwitchUndo(RenameEntry entry)
        {
            var oldName = entry.FullName;
            entry.FullName = entry.Destination;
            entry.Destination = oldName;
            return entry;
        }

        protected bool SuspendRename { get; set; }

        #endregion

        #region インデクサ

        private RenameEntry GetFilteredItem(int index)
        {
            lock (_entriesLocker)
                return filtered ? _entries.Where(e => IsRenamed(e)).ElementAtOrDefault(index) : _entries[index];
        }

        public string GetFullName(int itemIndex, bool filtered = true)
        {
            if (0 <= itemIndex && itemIndex < Count)
                return GetFilteredItem(itemIndex).FullName;
            return null;
        }

        public bool IsRenamed(int itemIndex)
        {
            if (filtered && filter_Renamed)
                return true;
            if (0 <= itemIndex && itemIndex < Count)
            {
                var item = GetFilteredItem(itemIndex);
                return IsRenamed(item);
            }
            return false;
        }

        private bool IsRenamed(RenameEntry entry)
        {
            return entry.NewName != entry.CurrentName.Text;
        }

        public int GetFilteredCount()
        {
            //TODO: CheckCountなんかから呼ばれたときに、スレッド間で_entriesの更新・参照が行われるため、例外が発生する。(2013/02/23)
            lock (_entriesLocker)
            {
                return _entries.Count(e => IsRenamed(e));
            }
        }

        [Browsable(false)]
        public int Count { get { return filtered ? GetFilteredCount() : _entries.Count; } }

        public object this[int index]
        {
            get { return GetFilteredItem(index); }
            set { }
        }
        
        #endregion

        #region ソート

        private void Sort()
        {
            lock (_entriesLocker)
                _entries.Sort(_sorter);
            _batch.ResetNumbers();
            lock (_entriesLocker)
            {
                int index = 0;
                IndexReplacer serial = new IndexReplacer();
                foreach (var entry in _entries)
                {
                    serial.Index = index++;
                    entry.SystemNewName = entry.CurrentName.Text;
                    SetNewName(serial, entry);
                }
            }
            RaiseListChanged(ListChangedType.Reset, -1);
        }

        public void ApplySort(ListSortDescriptionCollection sorts)
        {
            _sorter.Clear();
            foreach (ListSortDescription sort in sorts)
                _sorter.AddIndex(sort.PropertyDescriptor, sort.SortDirection);
            Sort();
        }

        public void ApplySort(PropertyDescriptor property, ListSortDirection direction)
        {
            _sorter.AddIndex(property, direction);
            Sort();
        }

        public void RemoveSort()
        {
            _sorter.Clear();
        }

        public ListSortDescriptionCollection SortDescriptions
        {
            get { return _sorter.GetSortDescriptions(); }
        }

        public bool IsSorted
        {
            get { return _sorter != null && _sorter.Count > 0; }
        }

        public PropertyDescriptor SortProperty
        {
            get { return _sorter != null ? _sorter.GetSortProperty() : null; }
        }

        [Browsable(false)]
        public ListSortDirection SortDirection
        {
            get { return _sorter.SortDirection; }
        }

        #endregion

        #region フィルタ/検索

        bool filtered;
        bool filter_Renamed;

        private bool IsMatch(RenameEntry entry)
        {
            if (!filtered)
                return true;
            return IsRenamed(entry) == filter_Renamed;
        }

        public string Filter
        {
            get
            {
                return string.Empty;
            }
            set
            {
                filter_Renamed = false;
                filtered = false;

                if (value == null)
                    return;

                foreach (var term in value.Split(',').Select(t => t.Trim().ToUpperInvariant()))
                {
                    switch (term)
                    {
                        case "RENAMED":
                            filtered = true;
                            filter_Renamed = true;
                            break;
                    }
                }

                RaiseListChanged(ListChangedType.Reset, -1);
            }
        }

        public void RemoveFilter()
        {
            filter_Renamed = false;
            filtered = false;

            RaiseListChanged(ListChangedType.Reset, -1);
        }

        public void AddIndex(PropertyDescriptor property)
        {
            throw new NotSupportedException();
        }

        public void RemoveIndex(PropertyDescriptor property)
        {
            throw new NotSupportedException();
        }

        public int Find(PropertyDescriptor property, object key)
        {
            throw new NotSupportedException();
        }

        #endregion

        #region リスト/リストビュー

        public bool AllowEdit
        {
            get { return true; }
        }

        public bool AllowNew
        {
            get { return false; }
        }

        public bool AllowRemove
        {
            get { return false; }
        }

        public bool IsFixedSize
        {
            get { return true; }
        }

        public bool IsReadOnly
        {
            get { return false; }
        }

        public bool IsSynchronized
        {
            get { return false; }
        }

        public bool SupportsAdvancedSorting
        {
            get { return true; }
        }

        public bool SupportsSorting
        {
            get { return true; }
        }

        public bool SupportsChangeNotification
        {
            get { return true; }
        }

        public bool SupportsFiltering
        {
            get { return true; }
        }

        public bool SupportsSearching
        {
            get { return false; }
        }

        public bool Contains(object value)
        {
            var entry = value as RenameEntry;
            return entry != null && _entries.Contains(entry);
        }

        public int IndexOf(object value)
        {
            var entry = value as RenameEntry;
            return entry != null ? _entries.IndexOf(entry) : -1;
        }

        public void CopyTo(Array array, int index)
        {
            var entries = array as RenameEntry[];
            if (entries != null)
                _entries.CopyTo(entries, index);
        }

        public object SyncRoot
        {
            get { return null; }
        }

        public System.Collections.IEnumerator GetEnumerator()
        {
            return _entries.GetEnumerator();
        }

        #endregion

        public event ListChangedEventHandler ListChanged;

        protected void OnListChanged(object sender, ListChangedEventArgs e)
        {
            var handler = ListChanged;
            if (handler != null)
                handler(sender, e);
        }

        private void RaiseListChanged(ListChangedType type, int index)
        {
            context.Post(new SendOrPostCallback(InvokeListChanged), new ListChangedEventArgs(type, index));
        }

        private void InvokeListChanged(object args)
        {
            OnListChanged(this, (ListChangedEventArgs)args);
        }

        #region 追加/削除

        public object AddNew()
        {
            throw new NotSupportedException();
        }

        public int Add(object value)
        {
            throw new NotSupportedException();
        }

        public void Clear()
        {
            throw new NotSupportedException();
        }

        public void Insert(int index, object value)
        {
            throw new NotSupportedException();
        }

        public void Remove(object value)
        {
            throw new NotSupportedException();
        }

        public void RemoveAt(int index)
        {
            throw new NotSupportedException();
        }

        #endregion

        public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
        {
            return TypeDescriptor.GetProperties(typeof(RenameEntry));
        }

        public string GetListName(PropertyDescriptor[] listAccessors)
        {
            return string.Empty;
        }
    }
}